﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UMLSynthesizer.LangAnalyzer.Elements;

namespace UMLSynthesizer.LangAnalyzer.ExtensionMethods
{
    public static class ElementExtension
    {
        #region General elements

        /// <summary>
        /// Return the path of names to reach this element
        /// </summary>
        public static string GetDefinitionPath(this IElementBase element)
        {
            List<string> reversedPathNames = new List<string>();
            IElementBase currentElement = element;
            while (currentElement != null && currentElement.Kind != ElementKinds.Project)
            {
                reversedPathNames.Add(currentElement.Identifier);
                currentElement = currentElement.ParentElement;
            }
            return PathToString((reversedPathNames as IEnumerable<string>).Reverse());
        }

        /// <summary>
        /// Return all types of dependency
        /// </summary>
        /// <param name="element">The element to analyze</param>
        /// <param name="visitedElements">The list of visited types</param>
        /// <returns>The type list</returns>
        public static IEnumerable<ITypeBase> GetDependenceTypes(this IElementBase element, List<IElementBase> visitedElements = null)
        {
            List<ITypeBase> typeDependenceList = new List<ITypeBase>();

            if (visitedElements == null)
                visitedElements = new List<IElementBase>();
            if (!visitedElements.Contains(element))
            {
                visitedElements.Add(element);
                if(element as ITypeBase != null)
                    typeDependenceList.Add(element as ITypeBase);

                foreach (var dependenceElement in element.GetUsedTypes())
                    typeDependenceList.AddRange(dependenceElement.GetDependenceTypes(visitedElements));
            }

            return typeDependenceList.Distinct().Where(type => type != null);
        }
        /// <summary>
        /// Return all types of directly used types
        /// </summary>
        /// <param name="element">The element to analyze</param>
        /// <param name="visitedElements">The list of visited types</param>
        /// <returns>The type list</returns>
        public static IEnumerable<ITypeBase> GetUsedTypes(this IElementBase element, List<IElementBase> visitedElements = null)
        {
            List<ITypeBase> typeUsedList = new List<ITypeBase>();

            if (visitedElements == null)
                visitedElements = new List<IElementBase>();
            if (!visitedElements.Contains(element))
            {
                visitedElements.Add(element);

                if (element as ITypeBase != null)
                {
                    if (element as IArrayType != null)
                        typeUsedList.AddRange((element as IArrayType).Type.GetUsedTypes(visitedElements));
                    else
                        typeUsedList.Add(element as ITypeBase);
                }
                if (element as IContainerOfElementsBase != null)
                    typeUsedList.AddRange((element as IContainerOfElementsBase)
                        .GetChildElements().SelectMany(subElement => subElement.GetUsedTypes(visitedElements)));
                if (element as IElementWithAccessorsBase != null)
                    typeUsedList.AddRange((element as IElementWithAccessorsBase)
                        .Accessors.SelectMany(accessor => accessor.GetUsedTypes(visitedElements)));
                if (element as IElementWithBodyBase != null &&
                    (element as IElementWithBodyBase).Body != null)
                    typeUsedList.AddRange((element as IElementWithBodyBase).Body.GetUsedTypes());
                if (element as IElementWithConstructorBase != null)
                    typeUsedList.AddRange((element as IElementWithConstructorBase)
                        .ContainedConstructors.SelectMany(constructor => constructor.GetUsedTypes(visitedElements)));
                if (element as IElementWithHeritageBase != null)
                    typeUsedList.AddRange((element as IElementWithHeritageBase).BaseList);
                if (element as IElementWithParametersBase != null)
                    typeUsedList.AddRange((element as IElementWithParametersBase)
                        .ParameterList.SelectMany(parameter => parameter.GetUsedTypes(visitedElements)));
                if (element as IElementWithTypeBase != null)
                    typeUsedList.AddRange((element as IElementWithTypeBase)
                        .Type.GetUsedTypes(visitedElements));
                if (element as IFieldElement != null)
                    typeUsedList.AddRange((element as IFieldElement).ContainedVariables
                        .SelectMany(variable => variable.GetUsedTypes(visitedElements)));
            }

            return typeUsedList.Distinct().Where(type => type != null);
        }

        #endregion

        #region Container elements

        /// <summary>
        /// Get all child elements
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        public static IEnumerable<IElementBase> GetChildElements(this IContainerOfElementsBase node)
        {
            List<IElementBase> elementList = new List<IElementBase>();
            elementList.AddRange(node.ContainedClasses);
            elementList.AddRange(node.ContainedDelegates);
            elementList.AddRange(node.ContainedEnums);
            elementList.AddRange(node.ContainedFields);
            foreach (var field in node.ContainedFields)
                elementList.AddRange(field.ContainedVariables);
            elementList.AddRange(node.ContainedInterfaces);
            elementList.AddRange(node.ContainedMethods);
            elementList.AddRange(node.ContainedNamespaces);
            elementList.AddRange(node.ContainedProperties);
            elementList.AddRange(node.ContainedStructs);

            return elementList;
        }
        /// <summary>
        /// Get all descendant elements from this
        /// </summary>
        /// <param name="root">The root element</param>
        /// <returns>List of descendant elements</returns>
        public static IEnumerable<IElementBase> GetDescendantElements(this IContainerOfElementsBase root)
        {
            List<IElementBase> elementList = new List<IElementBase>();

            var childElements = root.GetChildElements();
            elementList.AddRange(childElements);
            foreach (var element in childElements)
            {
                var containerElement = element as IContainerOfElementsBase;
                if (containerElement != null)
                    elementList.AddRange(GetDescendantElements(containerElement));
            }

            return elementList;
        }
        /// <summary>
        /// Get the element that starting from this root, is definied at this path
        /// </summary>
        /// <param name="root">The starting element</param>
        /// <param name="path">The path to follow</param>
        /// <returns>The element reached, null if the element doesn't exist</returns>
        public static IElementBase GetContainedElement(this IContainerOfElementsBase root, string path)
        {
            IElementBase element = root;
            IContainerOfElementsBase currentContainer = root;
            IEnumerable<string> pathNames = path.Split(new char[] { '.' });

            foreach (var name in pathNames)
            {
                if (currentContainer == null)
                    return null; //element not found
                element = (from childElement in currentContainer.GetChildElements()
                           where childElement.Identifier == name
                           select childElement).FirstOrDefault();
                currentContainer = element as IContainerOfElementsBase;
            }

            return element;
        }
        /// <summary>
        /// Return the first parent namespace of the element
        /// </summary>
        /// <param name="element">The element to analyze</param>
        /// <returns>The parent namespace, or null if it doesn't exist</returns>
        public static INamespaceElement GetParentNamespace(this IElementBase element)
        {
            IElementBase parentElement = element.ParentElement;
            while (parentElement != null && parentElement.Kind != ElementKinds.Namespace)
                parentElement = parentElement.ParentElement;

            if (parentElement != null)
                return parentElement as INamespaceElement;
            else
                return null;
        }

        #endregion

        #region Private Helper

        /// <summary>
        /// Print a path, eventualy a part of it
        /// </summary>
        /// <param name="path">The path to print</param>
        /// <param name="deep">The deep of path to print. -1 to print all path</param>
        /// <returns>The string of path</returns>
        static string PathToString(IEnumerable<string> path, int deep = -1)
        {
            string result = "";
            for (int i = 0; i < path.Count() && (deep < 0 || i <= deep); i++)
                result += (i == 0 ? path.ElementAt(i) : '.' + path.ElementAt(i));
            return result;
        }

        #endregion
    }
}
